<template>
  <div class="inject-app inject-scroll" ref="rootRef" :class="{ 'inject-app--lock': publicState.pin, 'inject-app--left': formState.positionLeft }">
    <div class="inject-hover-bar">
      <el-button circle :icon="IconPin" :type="publicState.pin ? 'primary' : undefined" size="small" @click="publicState.pin = !publicState.pin" />
      <el-button circle :icon="IconMeno" :type="formState.positionLeft ? 'primary' : undefined" size="small" @click="formState.positionLeft = !formState.positionLeft" />
    </div>
    <div class="form-item">
      <el-space wrap>
        <el-dropdown :teleported="false" placement="bottom-start" popper-class="shadow">
          <el-button :icon="IconBackups" />
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item @click="recoverDialog.open">数据恢复</el-dropdown-item>
              <el-dropdown-item @click="onBackups">数据备份</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
        <el-select
          clearable
          :teleported="false"
          :model-value="publicState.select"
          style="width: 200px"
          popper-class="shadow"
          placeholder="请选择项目"
          @update:modelValue="onSelect"
        >
          <el-option v-for="{ key } in state" :key="key" :label="key" :value="key" />
        </el-select>
        <el-dropdown :teleported="false" popper-class="shadow">
          <el-button :icon="IconOperation" />
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :icon="IconPlus" @click="onObjectEdit(true)">新增项目</el-dropdown-item>
              <el-dropdown-item :icon="IconEdit" :disabled="!publicState.object" @click="onObjectEdit(false)">重命名当前项目</el-dropdown-item>
              <el-dropdown-item :icon="IconClose" :disabled="!publicState.object" @click="onClear">删除当前项目</el-dropdown-item>
              <el-dropdown-item :icon="IconDelete" @click="onClearAll">删除所有项目</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
        <el-tooltip :append-to="rootRef" content="导入">
          <el-button :icon="IconUpload" @click="fileDialog.open" />
        </el-tooltip>
        <el-tooltip :append-to="rootRef" content="导出">
          <el-button :disabled="!publicState.object" :icon="IconDownload" @click="onExport" />
        </el-tooltip>
        <el-popover :teleported="false" :width="350" placement="bottom-end" title="配置" popper-class="shadow" v-if="publicState.object">
          <template #reference>
            <el-button :icon="IconSetting" />
          </template>
          <el-form :model="publicState.object" label-width="auto">
            <el-form-item label="别名前缀">
              <el-input placeholder="请输入拷贝别名前缀" v-model="publicState.object.space" />
            </el-form-item>
            <el-form-item label="别名风格">
              <el-select :teleported="false" popper-class="shadow" v-model="publicState.object.joint">
                <el-option :value="ColorAliasJoint.UpperCamelCase" label="大驼峰（ColorAliasJoint）" />
                <el-option :value="ColorAliasJoint.LowerCamelCase" label="小驼峰（colorAliasJoint）" />
                <el-option :value="ColorAliasJoint.Underline" label="下划线（color_alias_joint）" />
                <el-option :value="ColorAliasJoint.TransverseLine" label="短横线（color-alias-joint）" />
              </el-select>
            </el-form-item>
            <el-form-item label="变量类型">
              <el-select :teleported="false" popper-class="shadow" v-model="publicState.object.vartype">
                <el-option :value="ColorVartype.LESS" label="less" />
                <el-option :value="ColorVartype.SCSS" label="scss" />
                <el-option :value="ColorVartype.CSS_VAR" label="css var" />
                <el-option :value="ColorVartype.TYPESCRIPT" label="typescript" />
              </el-select>
            </el-form-item>
            <el-form-item label="导出 JS" v-if="publicState.object.vartype !== ColorVartype.TYPESCRIPT">
              <el-checkbox v-model="publicState.object.jsvar">导出 js 变量</el-checkbox>
            </el-form-item>
            <el-form-item label="颜色添加">
              <el-checkbox v-model="publicState.object.eyeConfirmed" label="直接记录颜色汲取器的值" />
              <el-checkbox v-model="publicState.object.pickerConfirmed" label="直接记录颜色选择器的值" />
              <el-checkbox v-model="publicState.object.realTimeMatching" label="实时匹配粘贴板中的颜色值" />
            </el-form-item>
          </el-form>
        </el-popover>
      </el-space>
    </div>
    <div class="left-item">
      <ColorConfirmed class="left-item-content" />
      <div class="left-item-footer">
        <el-tooltip :append-to="rootRef" content="吸取颜色">
          <el-button :disabled="!eyeDropper.isSupported" :icon="IconAim" @click="onEyeOpen()" />
        </el-tooltip>
        <el-color-picker show-alpha :append-to="rootRef" :disabled="!publicState.object" :validate-event="false" v-model="formState.inputColor" @change="onColorPickerChange" />
        <el-tooltip :append-to="rootRef" content="颜色查询">
          <el-input clearable :disabled="!publicState.object" placeholder="请输入颜色值或别名" class="search-input" v-model="publicState.searchColor" />
        </el-tooltip>
        <ColorView compute-append :color="matchedColor" v-if="matchedColor" />
      </div>
    </div>
    <div class="right-item">
      <ColorUnconfirmed />
    </div>
  </div>
  <DefineTemplate>
    <el-form label-width="auto" label-position="top">
      <el-form-item label="导入内容与当前内容不一致" style="margin: 0px">
        <el-radio-group v-model="conflictData.value">
          <el-radio :value="ConflictResolution.None">丢弃</el-radio>
          <el-radio :value="ConflictResolution.Cover">覆盖</el-radio>
          <el-radio :value="ConflictResolution.Merge">合并</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>
  </DefineTemplate>
</template>

<script lang="ts">
interface InjectAppContent {
  rootRef: Ref<HTMLDivElement | undefined>
}

interface FormState {
  inputColor?: string
  positionLeft?: boolean
}

const InjectAppKey: InjectionKey<InjectAppContent> = Symbol()

export function useAppInject() {
  return inject(InjectAppKey, { rootRef: ref() })
}
</script>

<script lang="ts" setup>
import { createReusableTemplate, useEyeDropper, useFileDialog } from '@vueuse/core'
import {
  Aim as IconAim,
  Delete as IconDelete,
  Download as IconDownload,
  Plus as IconPlus,
  Upload as IconUpload,
  Setting as IconSetting,
  Memo as IconMeno,
  Operation as IconOperation,
  CloseBold as IconClose,
  Edit as IconEdit
} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { type InjectionKey, type Ref, ref, inject, provide, reactive, computed, h } from 'vue'
import tinycolor from 'tinycolor2'
import { saveAs } from 'file-saver'
import { useColorStore } from '@/inject/stores/color'
import { FileReaderType, eliminate, fileReader, logError, merge } from '@/shared/utils'
import { type ColorObject, ColorVartype, ColorAliasJoint, colorObject2Text, colorPostfix, toHex, variateRegExp, setColorAlias } from '@/shared/color'
import { ColorConfirmed } from '../ColorConfirmed'
import { ColorUnconfirmed } from '../ColorUnconfirmed'
import { ColorView } from '../ColorView'
import { IconBackups, IconPin } from '../Icons'
import { PROJECT_UK } from '@/shared/const'

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()

defineOptions({ name: 'InjectApp' })

const rootRef = ref<HTMLDivElement>()

provide(InjectAppKey, { rootRef })

const { state, publicState, append, clear: onClear, clearAll: onClearAll, select: onSelect } = useColorStore()

const eyeDropper = useEyeDropper()

function onEyeOpen() {
  eyeDropper.open().then((data) => {
    if (data?.sRGBHex) append(data.sRGBHex, publicState.object?.eyeConfirmed)
  }, logError)
}

/**
 * 新增项目
 */
function onObjectEdit(isAdd: boolean) {
  return ElMessageBox.prompt(isAdd ? '添加项目' : '重命名', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    appendTo: rootRef.value,
    inputPlaceholder: '请输入项目名称',
    overflow: true,
    customStyle: { width: '350px' },
    lockScroll: false,
    inputValidator(value) {
      value = value.trim()
      if (state.find(({ key }) => key === value)) {
        return `${value} 已存在`
      }
      if (value.length > 10) {
        return '最多 10 个字符'
      }
      return value.length > 0
    }
  }).then(({ value }) => {
    value = value.trim()
    if (isAdd) {
      onSelect(value, true)
    } else {
      publicState.select = value
      publicState.object!.key = value
    }
  })
}

const formState = reactive<FormState>({})

/**
 * 颜色搜素的匹配结果
 */
const matchedColor = computed<string | void>(() => {
  const { object, searchColor } = publicState
  if (!object || !searchColor) return undefined

  const item = Object.entries(object.alias).find((item) => item[1] === searchColor)
  if (item) return item[0]

  const color = tinycolor(searchColor)
  if (color.isValid()) {
    return toHex(color)
  }

  return undefined
})

/**
 * 颜色选择器 change 回调
 */
function onColorPickerChange() {
  const { inputColor } = formState

  inputColor &&
    append(inputColor, publicState.object?.pickerConfirmed).then(() => {
      ElMessage({ type: 'success', message: '已添加' })
    }, logError)
}

/**
 * 导出当前项目
 */
function onExport() {
  const { object } = publicState
  colorObject2Text(object).then(
    (text) => {
      saveAs(new Blob([text]), `variate${colorPostfix[object!.vartype]}`)
    },
    (error: string) => {
      ElMessage({
        type: 'error',
        message: error,
        appendTo: rootRef.value
      })
    }
  )
}

interface Backups {
  uk: string
  data: string
}

/**
 * 数据备份
 */
function onBackups() {
  const data: Backups = { uk: PROJECT_UK, data: btoa(JSON.stringify(state)) }
  saveAs(new Blob([btoa(JSON.stringify(data))], { type: 'text/plain' }), 'backups.txt')
}

/**
 * 数据恢复
 */
const recoverDialog = useFileDialog({ accept: '.txt', reset: true })

recoverDialog.onChange((files) => {
  if (!files?.length) return

  fileReader(files[0], FileReaderType.TEXT).then(
    (text) => {
      try {
        const backups = JSON.parse(atob(text)) as unknown as Backups
        if (backups.uk !== PROJECT_UK) return
        const list = JSON.parse(atob(backups.data)) as unknown as ColorObject[]
        if (!Array.isArray(list) || !list.length) {
          return ElMessage.warning({
            message: '已恢复 0 个项目！',
            appendTo: rootRef.value
          })
        }

        const isCollide = list.some((newObject) => {
          return state.find(({ key }) => newObject.key === key)
        })

        if (isCollide) {
          conflictResolutionPrompt().then(({ value }) => {
            if (value === ConflictResolution.None) return
            if (value === ConflictResolution.Cover) {
              list.forEach((newObject) => {
                eliminate({
                  array: state,
                  validater(oldObject) {
                    return oldObject.key === newObject.key
                  }
                })
                state.push(newObject)
                if (newObject.key === publicState.select) {
                  onSelect(publicState.select)
                }
              })
            } else {
              list.forEach((newObject) => {
                const oldObject = state.find(({ key }) => newObject.key === key)!
                merge(oldObject, newObject)
              })
            }

            ElMessage.success({
              message: `已恢复 ${list.length} 个项目！`,
              appendTo: rootRef.value
            })

            !publicState.select && onSelect(state.at(-1)?.key)
          })
        } else {
          state.push(...list)
          ElMessage.success({
            message: `已恢复 ${list.length} 个项目！`,
            appendTo: rootRef.value
          })

          !publicState.select && onSelect(state.at(-1)?.key)
        }
      } catch (error) {
        ElMessage.error({
          message: '数据解析失败',
          appendTo: rootRef.value
        })
      }
    },
    (error) => {
      ElMessage.error({
        message: error,
        appendTo: rootRef.value
      })
    }
  )
})

/**
 * 导入到当前项目
 */
const fileDialog = useFileDialog({
  accept: '.scss,.less,.css',
  multiple: false,
  reset: true
})

enum ConflictResolution {
  /** 取消 */
  None,
  /** 覆盖 */
  Cover,
  /** 合并 */
  Merge
}

const conflictData = reactive({ value: ConflictResolution.None })

function conflictResolutionPrompt() {
  conflictData.value = ConflictResolution.None
  return ElMessageBox({
    title: '冲突解决方式',
    appendTo: rootRef.value,
    message: h(ReuseTemplate),
    customStyle: { width: '320px' },
    lockScroll: false
  }).then(
    () => ({ value: conflictData.value }),
    () => ({ value: ConflictResolution.None })
  )
}

fileDialog.onChange((files) => {
  if (!files?.length) return

  fileReader(files[0], FileReaderType.TEXT).then(
    (source) => {
      const { object } = publicState
      const promise: Promise<{ value: ConflictResolution }> = !object
        ? onObjectEdit(true).then(() => ({ value: ConflictResolution.Merge }))
        : JSON.stringify(object.confirmed) === '{}'
          ? Promise.resolve({ value: ConflictResolution.Cover })
          : conflictResolutionPrompt()

      promise.then(({ value }) => {
        if (value === ConflictResolution.None) return
        if (value === ConflictResolution.Cover) {
          Object.assign<ColorObject, Partial<ColorObject>>(publicState.object!, { confirmed: {} })
        }

        const { space } = publicState.object!
        source.replace(variateRegExp, (matched: string, _: string, key: string, value: string) => {
          append(value.trim(), true).then((color) => {
            if (space) key = key.replace(space, '')
            setColorAlias(publicState.object, color, key)
          })
          return matched
        })
      }, logError)
    },
    (error) => {
      ElMessage({
        type: 'error',
        message: error,
        appendTo: rootRef.value
      })
    }
  )
})
</script>

<style lang="scss" scoped>
.inject-app {
  position: fixed;
  top: 0;
  width: 535px;
  height: 500px;
  box-shadow:
    0px -11px 48px -5px rgba(0, 0, 0, 0.08),
    0px 0px 30px rgba(0, 0, 0, 0.12),
    0px 14px 16px -8px rgba(0, 0, 0, 0.16);
  display: grid;
  grid-template-columns: auto 125px;
  grid-template-rows: max-content auto;
  grid-template-areas:
    'form form'
    'left right';
  background: #ffffff;
  z-index: 10000000;

  &:not(.inject-app--left) {
    right: 0;
    transform: translateX(100%);
    // transition: transform 0.5s ease-in-out 0.1s;

    &:hover {
      transform: translateX(0);
    }

    .inject-hover-bar {
      left: 0;
      transform: translateX(-100%);
    }
  }

  &.inject-app--left {
    left: 0;
    transform: translateX(-100%);
    // transition: transform 0.5s ease-in-out 0.1s;

    &:hover {
      transform: translateX(0);
    }

    .inject-hover-bar {
      right: 0;
      transform: translateX(100%);
    }
  }

  &.inject-app--lock {
    transform: none;
  }

  .inject-hover-bar {
    display: inline-block;
    position: absolute;
    top: 0;
    height: 100%;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-top: 20px;
    box-sizing: border-box;

    :deep(.el-button) {
      margin: 0;
    }
  }

  .form-item {
    grid-area: form;
    overflow: unset;
    padding: 10px;
    border-bottom: 1px solid #e5e5e5;
  }

  .left-item {
    grid-area: left;
    border-right: 1px solid #e5e5e5;
    overflow: hidden;
  }

  .left-item-footer {
    display: flex;
    column-gap: 10px;
    padding: 10px;
    border-top: 1px solid #e5e5e5;
  }

  .right-item {
    grid-area: right;
    padding: 1px 10px;
    margin: 10px 0;
    overflow-y: auto;
  }

  .left-item,
  .right-item {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;

    & > :deep(.el-card__footer) {
      padding: 10px;
      display: flex;
      column-gap: 10px;
    }

    & > :deep(.el-card__body) {
      flex: 1;
      overflow-y: auto;
      margin: 10px 0;
    }
  }

  .search-input {
    width: 150px;
    transition: width ease-in-out 0.5s;

    &:hover,
    &:focus {
      width: 150px;
    }
  }

  :deep(.el-overlay.is-message-box),
  :deep(.el-overlay-message-box),
  :deep(.el-message) {
    position: absolute;
  }

  :deep(.el-button) {
    padding: 8px;
  }

  :deep(.shadow) {
    box-shadow: var(--el-box-shadow-dark);
  }
}

.column-interval {
  height: 100%;
  border-right: 1px dashed #a8abb2;
}
</style>
